﻿// Copyright (c) Microsoft Corporation.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Metadata.Edm;
using System.Web.UI.WebControls;
using System.Data.Objects.DataClasses;
using System.Data;
using System.Data.EntityClient;
using System.Xml.Linq;
using System.IO;

namespace EdmMetadataHelpers
{
    /// <summary>
    /// This class acts as a loosely bound wrapper for an EDM at the Entity (Value) Layer.
    /// </summary>
    public class EdmWrapper
    {
        #region Constants
        public const string DEFAULT_PROVIDER = "System.Data.SqlClient";
        public const string DEFAULT_PROVIDER_CONN_STR = @"Data Source=.\SQLEXPRESS;Initial Catalog=[Database];Integrated Security=True";
        public const string EDM_CONN_STR = @"metadata={csdl}|{ssdl}|{msl};provider={provider};provider connection string=""{provider_connection_string}""";
        #endregion

        #region Member Variables
        EdmCsdlCatalog _csdl;
        string _name;
        string _provider;
        string _providerConnectionString;
        string _csdlPath;
        string _ssdlPath;
        string _mslPath;
        #endregion

        #region Constructors
        /// <summary>
        /// Construct a wrapper around an Edm model/database
        /// </summary>
        /// <param name="name">A name to give this model</param>
        /// <param name="provider">The provider</param>
        /// <param name="providerConnectionString">The connection string for the provider</param>
        /// <param name="csdlPath">The path to the conceptual model file (*.csdl)</param>
        /// <param name="ssdlPath">The path to the store model file (*.ssdl)</param>
        /// <param name="mslPath">The path to the C-S space mapping file (*.msl)</param>
        public EdmWrapper(string name, string provider, string providerConnectionString, string csdlPath, string ssdlPath, string mslPath)
        {
            _name = name;
            _provider = provider;
            _providerConnectionString = providerConnectionString;
            _csdlPath = csdlPath;
            _ssdlPath = ssdlPath;
            _mslPath = mslPath;
        }
        #endregion

        #region Properties
        public string Name { get { return _name; } }
        public string Provider { get { return _provider; } }
        public string ProviderConnectionString { get { return _providerConnectionString; } }
        public string CsdlPath { get { return _csdlPath; } }
        public string SsdlPath { get { return _ssdlPath; } }
        public string MslPath { get { return _mslPath; } }
        public string EdmConnectionString { get { return GetEdmConnectionString(Provider, ProviderConnectionString, CsdlPath, SsdlPath, MslPath); } }
        /// <summary>
        /// Get a shredded view of the CSDL metadata
        /// </summary>
        public EdmCsdlCatalog Csdl
        {
            get {
                if (_csdl == null)
                    _csdl = new EdmCsdlCatalog(_csdlPath);
                return _csdl;
            }
        }
        #endregion

        #region Public methods
        /// <summary>
        /// Get this Edm as a TreeNode + SubNodes
        /// </summary>
        public TreeNode GetRootNode()
        {
            TreeNode node = new TreeNode(Name);
            node.ImageUrl = @"assets\schema.jpg";
            node.Target = "Model";
            node.SelectAction = TreeNodeSelectAction.Select;
            
            foreach (TreeNode snode in Csdl.Containers.Select(c => GetNode(c)))
                node.ChildNodes.Add(snode);
            

            return node;
        }
        /// <summary>
        /// Execute the eSQL provided against the Edm and convert the results to a DataTable
        /// </summary>
        /// <param name="eSQL">Entity SQL to execute</param>
        /// <returns>a DataTable of results</returns>
        public DataTable GetDataTable(string eSQL)
        {
            using (EntityConnection conn = new EntityConnection(EdmConnectionString))
            {
                conn.Open();
                using (EntityCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = eSQL;
                    using (EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
                    {
                        return EdmDataReaderAdapter.LoadTable(reader);
                    }
                }
            }
        }
        /// <summary>
        /// Execute the eSQL provided against the Edm and materialize the results as an 
        /// enumeration of XElements
        /// </summary>
        /// <param name="eSQL">Entity SQL to execute</param>
        /// <returns>an enumeration of XElements</returns>
        public IEnumerable<XElement> GetElements(string eSQL)
        {
            using (EntityConnection conn = new EntityConnection(EdmConnectionString))
            {
                conn.Open();
                using (EntityCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandText = eSQL;
                    using (EntityDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
                    {
                        //we do a to list to fill get all the XElements before the reader gets closed!
                        return EdmXmlAdapter.GetElements(reader).ToList();
                    }
                }
            }
        }
        #endregion

        #region Static Utility methods
        /// <summary>
        /// A simple utility method that builds an EDM connection string
        /// </summary>
        /// <param name="provider">the full .NET classname of the provider</param>
        /// <param name="providerConnectionString">the connection string for that provider</param>
        /// <param name="csdlPath">the path to the CSDL file</param>
        /// <param name="ssdlPath">the path to the SSDL file</param>
        /// <param name="mslPath">the path to the MSL file</param>
        /// <returns></returns>
        public static string GetEdmConnectionString(string provider, string providerConnectionString, string csdlPath, string ssdlPath, string mslPath)
        {
            EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder();
            builder.Metadata = string.Format("{0}|{1}|{2}", csdlPath, ssdlPath, mslPath);
            builder.Provider = provider;
            builder.ProviderConnectionString = providerConnectionString;
            return builder.ConnectionString;
        }
        /// <summary>
        /// Convert a RelationshipMultiplicity to a string
        /// </summary>
        public static string GetCardinalityAsString(RelationshipMultiplicity multiplicity)
        {
            if (multiplicity == RelationshipMultiplicity.Many)
                return "0-*";
            else if (multiplicity == RelationshipMultiplicity.ZeroOrOne)
                return "0-1";
            else
                return "1";

        }
        #endregion

        #region Node factory methods for various metadata elements
        protected TreeNode GetNode(AssociationType association)
        {
            TreeNode node = new TreeNode(association.FullName);
            node.ImageUrl = @"assets\associationtype.jpg";
            node.SelectAction = TreeNodeSelectAction.Select;
            node.Target = "AssociationType";
            return node;
        }
        protected TreeNode GetNode(EntityTypeBase entityBase)
        {
            TreeNode node = new TreeNode(entityBase.FullName);

            node.ImageUrl = @"assets\entitytype.jpg";
            node.SelectAction = TreeNodeSelectAction.SelectExpand;
            node.Target = "EntityType";

            EntityType et = entityBase as EntityType;
            if (et != null)
            {
                foreach (TreeNode snode in et.Properties.Select(p => GetNode(p)))
                    node.ChildNodes.Add(snode);


                foreach (TreeNode snode in et.NavigationProperties.Select(np => GetNode(np)))
                    node.ChildNodes.Add(snode);
            }
            return node;
        }
        protected TreeNode GetNode(EdmProperty property)
        {
            TreeNode node = new TreeNode(property.Name);
            if (property.IsEntityKey())
                node.ImageUrl = @"assets\key.jpg";
            else
                node.ImageUrl = @"assets\property.jpg";
            node.SelectAction = TreeNodeSelectAction.Select;
            node.Target = "EdmProperty";

            //Add a node for the type and facets
            node.ChildNodes.Add(new TreeNode(string.Format("Type='{0}'", property.TypeUsage.EdmType.FullName)));
            foreach (Facet facet in property.TypeUsage.Facets)
                node.ChildNodes.Add(GetNode(facet));
            return node;
        }
        protected TreeNode GetNode(Facet facet)
        {
            if (facet.Value == null)
                return new TreeNode(string.Format("{0} is not set", facet.Name));
            else
                return new TreeNode(string.Format("{0}='{1}'", facet.Name, facet.Value.ToString()));
        }
        protected TreeNode GetNode(NavigationProperty navigationProperty)
        {
            TreeNode node = new TreeNode(string.Format("{0} => {1} {2}", navigationProperty.Name, GetCardinalityAsString(navigationProperty.ToEndMember.RelationshipMultiplicity), navigationProperty.ToEndMember.Name));
            node.ImageUrl = @"assets\relationship.jpg";
            node.SelectAction = TreeNodeSelectAction.Select;
            node.Target = "NavigationProperty";

            node.ChildNodes.Add(AddPrefix("From: ", GetNode(navigationProperty.FromEndMember)));
            node.ChildNodes.Add(AddPrefix("To: ", GetNode(navigationProperty.ToEndMember)));
            return node;
        }
        protected TreeNode AddPrefix(string prefix, TreeNode node)
        {
            node.Text = prefix + node.Text;
            return node;
        }
        protected TreeNode GetNode(RelationshipEndMember relationshipEndMember)
        {
            TreeNode node = new TreeNode(string.Format("{0} ({1})", relationshipEndMember.Name, GetCardinalityAsString(relationshipEndMember.RelationshipMultiplicity)));
            return node;
        }
        protected TreeNode GetNode(EntityContainer entityContainer)
        {
            TreeNode node = new TreeNode(entityContainer.Name);
            node.Target = "EntityContainer";
            node.ImageUrl = @"assets\container.jpg";
            node.SelectAction = TreeNodeSelectAction.Expand;

            foreach (TreeNode snode in entityContainer.BaseEntitySets.OfType<EntitySet>().Select(es => GetNode(es)))
                node.ChildNodes.Add(snode);
            return node;
        }
        protected TreeNode GetNode(EntitySetBase entitySetBase)
        {

            bool IsEntitySet = (entitySetBase is EntitySet);
            string containedType = entitySetBase.ElementType.Name;

            TreeNode node = new TreeNode(entitySetBase.Name);
            node.ToolTip = string.Format("This is an EntitySet of type '{0}'", containedType);
            node.SelectAction = TreeNodeSelectAction.SelectExpand;
            node.ImageUrl = IsEntitySet ? @"assets\entityset.jpg" : @"assets\associationset.jpg";
            node.Target = IsEntitySet ? "EntitySet" : "AssociationSet";

            node.ChildNodes.Add(GetNode(entitySetBase.ElementType));
            return node;
        }
        #endregion
    }
}
